package com.kilman.logger.ssh;

import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.taskdefs.optional.ssh.SSHBase;

import com.jcraft.jsch.ChannelExec;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;

/* code taken and adapted from SSHExec of ant-jsch */
public class ContinuousSSHExecution extends SSHBase implements Runnable {

    private static final int RETRY_INTERVAL = 1000;

    private String command = null;
    private long maxwait = 0;
    private Thread thread = null;
    private Boolean endExecution = false;
    private List<String> linesInternal = null;
    private List<String> lines = null;
    
    private static final String TIMEOUT_MESSAGE = "Timeout period exceeded, connection dropped.";

    public ContinuousSSHExecution(String command, long timeout, String host, String username, String password) {
        super();
        setFailonerror(false);
        setTrust(true);
        setHost(host);
        setUsername(username);
        setPassword(password);
        setCommand(command);
        setTimeout(timeout);
        linesInternal = new ArrayList<String>();
        lines = Collections.synchronizedList(linesInternal);
    }

    /** lines should only be accessed sync */
    public List<String> getOutput() {
        return lines;
    }
    
    public List<String> getCurrentOutput() {
        synchronized (lines) {
            return new ArrayList<String>(lines);
        }
    }
    
    public void setCommand(String command) {
        this.command = command;
    }

    public String getCommand() {
        return command;
    }
    
    public void setTimeout(long timeout) {
        maxwait = timeout;
    }

    public void endExecution() {
        synchronized(endExecution) {
            endExecution = true;
        }
    }
    
    public void execute() throws BuildException {
        if (getHost() == null) {
            throw new BuildException("Host is required.");
        }
        if (getUserInfo().getName() == null) {
            throw new BuildException("Username is required.");
        }
        if (getUserInfo().getKeyfile() == null
            && getUserInfo().getPassword() == null) {
            throw new BuildException("Password or Keyfile is required.");
        }
        if (command == null) {
            throw new BuildException("Command is required.");
        }

        Session session = null;
        try {
            session = openSession();
            log("cmd : " + command, Project.MSG_INFO);
            executeCommand(session, command);
        } catch (JSchException e) {
            if (getFailonerror()) {
                throw new BuildException(e);
            } else {
                log("Caught exception: " + e.getMessage(), Project.MSG_ERR);
            }
        } finally {
            if (session != null && session.isConnected()) {
                session.disconnect();
            }
        }
    }

    private void executeCommand(Session session, String cmd)
        throws BuildException {

        final ChannelExec channel;
        
        final ByteArrayOutputStream out = new ByteArrayOutputStream();
        
        try {
            session.setTimeout((int) maxwait);
            channel = (ChannelExec) session.openChannel("exec");
            channel.setCommand(cmd);
            channel.setOutputStream(out);
            channel.setExtOutputStream(out);
            channel.connect();
            thread = 
                new Thread() {
                    public void run() {
                        while (!channel.isClosed()) {
                            synchronized (endExecution) {
                                if (endExecution) {
                                    thread = null;
                                    return;
                                }
                            }
                            
                            try {
                                
                                sleep(RETRY_INTERVAL);
                                
                                String currentOutput = "";
                                synchronized(out) {
                                    currentOutput = out.toString();
                                    out.reset();
                                }
                                
                                StringReader sReader = new StringReader(currentOutput);
                                BufferedReader reader = new BufferedReader(sReader);
                                
                                String line = reader.readLine();
                                
                                synchronized(lines) {
                                    while (line != null) {
                                        lines.add(line);
                                        line = reader.readLine();
                                    }
                                }
                                    

                            } catch (Exception e) {
                                // ignored
                            }
                        }
                    }
                };

            thread.start();
            thread.join(maxwait);
            
            if (channel.isConnected()) {
                channel.disconnect();
            }
            
            int ec = channel.getExitStatus();
            if (ec != 0) {
                String msg = "Remote command failed with exit status " + ec;
                log(msg, Project.MSG_ERR);
            }

        } catch (BuildException e) {
            throw e;
        } catch (JSchException e) {
            if (e.getMessage().indexOf("session is down") >= 0) {
                log(TIMEOUT_MESSAGE, Project.MSG_ERR);
            } else {
                log("Caught exception: " + e.getMessage(), Project.MSG_ERR);
            }
        } catch (Exception e) {
            log("Caught exception: " + e.getMessage(), Project.MSG_ERR);
        } 
    }

    public void run() {
        execute();
    }

}
